package next.fire.spinus.logx.provider.aliyun.client;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import next.fire.spinus.logx.base.AbstractClient;
import next.fire.spinus.logx.provider.aliyun.conf.AliyunLogConfig;
import next.fire.spinus.logx.provider.aliyun.conf.AliyunLogConfigHolder;
import next.fire.spinus.logx.utils.codec.MD5;
import next.fire.spinus.logx.utils.codec.URLEncodeHelper;
import next.fire.spinus.logx.utils.http.FireHttpUtils;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * Created by daibing on 2020/9/15.
 */
public class AliyunLogClient extends AbstractClient {
    public static final String LOG_REQUEST_ID = "x-log-requestid";
    public static final String LOG_PROGRESS = "x-log-progress";
    public static final String LOG_COUNT = "x-log-count";
    public static final String RESPONSE_DATA_LIST = "RESPONSE_DATA_LIST";
    private static final String CONTENT_TYPE = "application/json";

    @Override
    protected byte[] buildRawBody(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams) {
        return new byte[0];
    }

    @Override
    protected byte[] buildEncodeBody(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams, byte[] rawBody) {
        return new byte[0];
    }

    @Override
    protected Map<String, String> buildHeader(Method method, String uri, String providerId, Map<String, Object> bizParams,
                                              Map<String, Object> accessParams, byte[] rawBody, byte[] encodeBody) {
        AliyunLogConfig config = AliyunLogConfigHolder.get().select(providerId);
        if (config == null) {
            throw new RuntimeException("aliyun log config not found by " + providerId);
        }
        String logResourceHost = AliyunHelper.getResourceHost(config.getAccountEndpoint(), config.getLogProject());
        String md5 = encodeBody.length > 0 ? MD5.md5(encodeBody).toUpperCase() : "";
        String date = AliyunHelper.getGmtString(new Date());
        Map<String, String> header = new TreeMap<>();
        header.put("Content-MD5", md5);
        header.put("Content-Type", CONTENT_TYPE);
        header.put("x-log-bodyrawsize", String.valueOf(rawBody.length));
        header.put("x-log-apiversion", "0.6.0");
        header.put("x-log-signaturemethod", "hmac-sha1");
        header.put("Host", logResourceHost);
        header.put("Date", date);
        return header;
    }

    @Override
    protected String buildSign(Method method, String uri, String providerId, Map<String, Object> bizParams,
                               Map<String, Object> accessParams, Map<String, String> header, byte[] rawBody, byte[] encodeBody) {
        AliyunLogConfig config = AliyunLogConfigHolder.get().select(providerId);
        if (config == null) {
            throw new RuntimeException("aliyun log config not found by " + providerId);
        }
        String canonicalizedHeaders = AliyunHelper.getCanonicalizedHeaders(header, "x-log-", "x-acs-");
        return AliyunHelper.sign(method.name(), header.get("Content-MD5"), header.get("Content-Type"), header.get("Date"),
                canonicalizedHeaders, uri, config.getAccessKeySecret());
    }

    @Override
    protected void loadSign(String providerId, Map<String, String> header, String sign) {
        AliyunLogConfig config = AliyunLogConfigHolder.get().select(providerId);
        if (config == null) {
            throw new RuntimeException("aliyun log config not found by " + providerId);
        }
        header.put("Authorization", AliyunHelper.getAuthorization("LOG ", config.getAccessKeyId(), sign));
    }

    @Override
    protected String buildUrl(String uri, String providerId, Map<String, Object> bizParams, Map<String, Object> accessParams, String sign) {
        AliyunLogConfig config = AliyunLogConfigHolder.get().select(providerId);
        if (config == null) {
            throw new RuntimeException("aliyun log config not found by " + providerId);
        }
        String protocol = AliyunHelper.getProtocol(config.getAccountEndpoint());
        String logResourceHost = AliyunHelper.getResourceHost(config.getAccountEndpoint(), config.getLogProject());

        Map<String, String> parameter = getParameter(uri);
        String queryString = this.paramToQueryString(parameter, StandardCharsets.UTF_8.name());
        uri = queryString == null ? uri : uri.substring(0, uri.indexOf("?")) + "?" + queryString;
        return protocol + logResourceHost + uri;
    }

    @Override
    protected JSONObject parse(Map<String, String> postResult, Map<String, Object> accessParams) {
        JSONObject output = new JSONObject();
        output.put(LOG_REQUEST_ID, postResult.get(LOG_REQUEST_ID));
        if (postResult.containsKey(LOG_PROGRESS)) {
            output.put(LOG_PROGRESS, postResult.get(LOG_PROGRESS));
        }
        if (postResult.containsKey(LOG_COUNT)) {
            output.put(LOG_COUNT, postResult.get(LOG_COUNT));
        }
        if (accessParams.containsKey(RESPONSE_DATA_LIST) && Boolean.parseBoolean(accessParams.get(RESPONSE_DATA_LIST).toString())) {
            JSONArray arr = JSON.parseArray(postResult.get(FireHttpUtils.RESPONSE_BODY));
            output.put(FireHttpUtils.RESPONSE_BODY, arr != null ? arr : new JSONArray(0));
        } else {
            JSONObject obj = JSON.parseObject(postResult.get(FireHttpUtils.RESPONSE_BODY));
            if (obj != null && !obj.isEmpty()) {
                output.putAll(obj);
            }
        }
        return output;
    }

    @Override
    protected boolean checkBody(JSONObject output, boolean skipCheckBodyStatus) {
        return true;
    }

    @Override
    protected boolean checkSign(String providerId, Map<String, String> rawOutput, JSONObject output) {
        return true;
    }

    private Map<String, String> getParameter(String uri) {
        int beginIndex = uri.indexOf("?");
        HashMap<String, String> params = new HashMap<>();
        if (beginIndex > 0) {
            String contents = uri.substring(uri.indexOf('?') + 1);
            String[] paramPairArr = contents.split("&");
            for (String paramPair : paramPairArr) {
                String paramKey = paramPair.substring(0, paramPair.indexOf("="));
                String paramValue = paramPair.substring(paramPair.indexOf("=") + 1);
                params.put(paramKey, paramValue);
            }
        }
        return params;
    }

    private String paramToQueryString(Map<String, String> params, String charset) {
        if (params == null || params.isEmpty()) {
            return null;
        }
        StringBuilder paramString = new StringBuilder();
        boolean first = true;
        for (Map.Entry<String, String> p : params.entrySet()) {
            String key = p.getKey();
            String value = p.getValue();
            if (!first) {
                paramString.append("&");
            }
            // Urlencode each request parameter
            paramString.append(URLEncodeHelper.percentEncode(key));
            if (value != null) {
                paramString.append("=").append(URLEncodeHelper.percentEncode(value));
            }
            first = false;
        }
        return paramString.toString();
    }

}
